No Engine
When it comes to game development, there are typically two routes one can take:
- Use an existing game engine.
- Make your own.
But it seems there is a third option, which I was only lightly aware of, but did not consider or understand properly until recently. That option is "no-engine" game development, and it might be a perfect option for me.
Idea is really simple, instead of building an engine which supports arbitrary features, you only build the parts which support the features you need to build your current game. If I'm building Minesweeper, I do not need an engine with support for advanced real-time physics simulation.
This comes with an obvious benefit of having much less work to do and the lean nature of such code probably results in a lighter runtime, which leaves more performance budget for the game itself. One obvious concern here is: what about code re-use? If you do not write a generic engine then you must start over from scratch every single time you want to make a game, right?
While it's true that this approach may result in more work during the early phase of any new project, it's never going to be more work than the first project. This is because, you can easily go back and look at the previous code for insight, but you can also copy parts of it over. Priority should therefore lie in making sure the code is well documented, so that it's really easy to understand it in the future.
It seems to me that this approach is sound, at least in theory. To test this approach, I've decided to bring one of my game ideas to life, and after that game is complete I will try re-using some of its code in the next game to see how it compares to using a generic engine.
Who knows, copy-paste may be the future of software development!
Koshchei
Project Koshchei is a commercial video game which I intend to release on steam. Placeholder name for this project comes from the tale of Koshchei the Deathless, who is an immortal sorcerer who hid his soul in a needle, inside an egg, within a duck, inside a hare, locked in an iron chest and buried under the tree on an island far away.
Why did I pick this name? Because I like the idea of a main villain such as Koshchei, and the game I intend to build will revolve around removing pieces of oneself to gain power. Just like Koshchei separated himself from his soul in order to protect himself from death, so will you trade your soul and body for power.
Tech
I'm going to build this game with Jai programming language and Raylib. Simply because both are really fun to work with and happiness is the easiest way to increase productivity.
One tricky bit here is that Raylib does not have official bindings for Jai, since Jai is not public yet. The way I solve that is with a function that I can call in the build metaprogram. This function will generate Raylib bindings if they are missing and wrap them into a module (similar to C++ library) so that we can easily import and namespace Raylib in this project.
generate_raylib_module : : (module_name: string , raylib_path: string ) {
using options: Generate_Bindings_Options;
strip_flags = 0 ;
generate_compile_time_struct_checks = false ;
alias_original_enum_names = false ;
c_enum_emulation = false ;
generate_library_declarations = false ;
// Add definitions for things that are missing in the { module.jai } header.
header = #string MODULE_HEADER
va_list : : * void ;
MODULE_HEADER
// Link against system libraries we need in the { module.jai } footer (order matters).
footer = #string MODULE_FOOTER
user32 : : #library,system,link_always,no_dll "user32" ;
gdi32 : : #library,system,link_always,no_dll "gdi32" ;
shell32 : : #library,system,link_always,no_dll "shell32" ;
winmm : : #library,system,link_always,no_dll "winmm" ;
raylib : : #library,no_dll "lib/raylib" ;
MODULE_FOOTER
// Add { Raylib } files to the generator.
raylib_lib_directory : = tprint("%/lib" , raylib_path);
raylib_include_directory : = tprint("%/include" , raylib_path);
array_add(* libpaths, raylib_lib_directory);
array_add(* libnames, "raylib" );
array_add(* include_paths, raylib_include_directory);
array_add(* source_files, ..string .["raylib.h" , "raymath.h" , "rlgl.h" ]);
// Generate module directory.
raylib_module_directory : = tprint("%/%" , LOCAL_MODULE_IMPORT_DIRECTORY, module_name);
raylib_module_lib_directory : = tprint("%/lib" , raylib_module_directory);
raylib_module_file : = tprint("%/module.jai" , raylib_module_directory);
// Recursively create directories for the Raylib module:
make_directory_if_it_does_not_exist(raylib_module_lib_directory, true );
// Generate bindings and save them as module.jai.
generate_bindings(options, raylib_module_file);
for string .["raylib.lib" ] {
file_src : = tprint("%/%" , raylib_lib_directory, it );
file_dest : = tprint("%/%" , raylib_module_lib_directory, it );
copy_file(file_src, file_dest);
}
}
This function can be called at the start of the build metaprogram when the module is missing:
// Generate Raylib module if necessary:
if ! file_exists("local_modules/raylib_win64" ) {
generate_raylib_module("raylib_win64" , "../vendor/raylib-5.0_win64_msvc16" );
}
As is tradition, we can test that Raylib works by creating a "Hello Window" program:
#import "raylib_win64" ;
main : : () {
InitWindow(800 , 600 , "Hello, Window!" );
defer CloseWindow();
while ! WindowShouldClose() {
BeginDrawing();
ClearBackground(.{50 , 100 , 150 , 255 });
EndDrawing();
}
}
This results in what is arguably the best looking window:
Time
When it comes to deadlines, I do not have a hard deadline, but I would like to have a shape of something fun within 2 months. But it all depends on the amount of time I have for this project, I speculate it will be close to 8 hours per week. Which is also one of the reasons I decided to go with Raylib.
Anyways, the prototyping begins now!